using System;
using System.Collections.Generic;

namespace IndiGoGameProcessor.Arithmetic
{
	/// <summary>
	/// Stores a value up to 3^5 = 243 in a byte
	/// </summary>
	public struct Tryte
	{
		private byte value;
	
		public Tryte(byte value)
		{
			this.value = value;
			if(value > MaxValue) { throw new OverflowException("Trytes can only hold values up to " + MaxValue); }
			if(value < MinValue) { throw new OverflowException("Trytes can only hold valyes greater than " + MinValue); }
		}
		
		#region Operator overloads
		
		// TODO:  implement IEquatable<T>, override Equals(object)
		
		public static implicit operator Tryte(byte value)
		{
			return new Tryte(value);
		}
		
		public static implicit operator byte(Tryte tryte)
		{
			return tryte.value;
		}
		
		public static implicit operator Tryte(int value)
		{
			return new Tryte((byte)value);
		}
		
		public static Tryte operator +(Tryte first, Tryte second)
		{
			return new Tryte((byte)(first.value + second.value));
		}
		
		public static Tryte operator -(Tryte first, Tryte second)
		{
			return new Tryte((byte)(first.value - second.value));
		}
		
		public static bool operator ==(Tryte first, Tryte second)
		{
			return first.value == second.value;
		}
		
		public static bool operator !=(Tryte first, Tryte second)
		{
			return first.value != second.value;
		}
		
		public static bool operator <(Tryte first, Tryte second)
		{
			return first.value < second.value;
		}
		
		public static bool operator >(Tryte first, Tryte second)
		{
			return first.value > second.value;
		}
		
		public static bool operator <=(Tryte first, Tryte second)
		{
			return first.value <= second.value;
		}
		
		public static bool operator >=(Tryte first, Tryte second)
		{
			return first.value >= second.value;
		}
		
		public override bool Equals (object obj)
		{
			return value.Equals (obj);
		}
		
		public override int GetHashCode ()
		{
			return value.GetHashCode ();
		}
		
		public override string ToString ()
		{
			return value.ToString();
		}
		
		#endregion
		
		public byte MaxValue
		{
			get{ return 243;}
		}
		
		public byte MinValue
		{
			get{return 0;}
		}
		
		public byte Add(byte trit, byte pos)
		{
			if(trit > 2) { throw new OverflowException(); }
			if(trit < 0) { throw new OverflowException(); }
			if(pos > 4) { throw new OverflowException(); }
			if(pos < 0) { throw new OverflowException(); }
			
			var toAdd = (byte)((byte)Math.Pow(3, pos)*trit);
			
			if(value + toAdd > MaxValue) { throw new OverflowException(); }
			
			value += toAdd;
			
			return value;
		}
	}
	
	public class Base3Handler
	{
		private static List<byte> bases = new List<byte>{1, 3, 9, 27, 81};
		
		public byte CreateByteFromTrits(List<byte> trits)
		{
			if(trits.Count > 5) { throw new InvalidOperationException(); }
			if(trits.FindAll(trit =>  trit != 0 && trit != 1 && trit != 2).Count != 0)
			{ throw new InvalidOperationException(); }
			
			byte pos = 0;
			byte theByte = 0;
			foreach(var trit in trits)
			{
				theByte += (byte)(trit*bases[pos]);
				pos++;
			}
			return theByte;
		}
		
		public List<byte> CreateTritsFromByte(byte theByte)
		{
			var trits = new List<byte>();
			
			do
			{
				trits.Add((byte)(theByte % 3));
				theByte /= 3;
				
			} while (theByte > 0);
			
			//Pad remainder with zeros.
			while(trits.Count < 5)
			{
				trits.Add(0);
			}
			
			return trits;
		}
	}
}
